{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 面向对象编程\n",
    "\n",
    "面向对象编程——Object Oriented Programming，简称OOP，是一种程序设计思想。OOP把对象作为程序的基本单元，一个对象包含了数据和操作数据的函数。\n",
    "\n",
    "面向过程的程序设计把计算机程序视为一系列的命令集合，即一组函数的顺序执行。为了简化程序设计，面向过程把函数继续切分为子函数，即把大块函数通过切割成小块函数来降低系统的复杂度。\n",
    "\n",
    "而面向对象的程序设计把计算机程序视为一组对象的集合，而每个对象都可以接收其他对象发过来的消息，并处理这些消息，计算机程序的执行就是一系列消息在各个对象之间传递。\n",
    "\n",
    "在Python中，所有数据类型都可以视为对象，当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类（Class）的概念。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 类和实例\n",
    "\n",
    "面向对象最重要的概念就是类（Class）和实例（Instance），必须牢记类是抽象的模板，比如Student类，而实例是根据类创建出来的一个个具体的“对象”，每个对象都拥有相同的方法，但各自的数据可能不同。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 用class关键字定义类：\n",
    "class Student(object):\n",
    "    pass"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "class后面紧接着是类名，即Student，类名通常是大写开头的单词，紧接着是(object)，表示该类是从哪个类继承下来的，继承的概念我们后面再讲，通常，如果没有合适的继承类，就使用object类，这是所有类最终都会继承的类。\n",
    "\n",
    "定义好了Student类，就可以根据Student类创建出Student的实例，创建实例是通过类名+()实现的："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<__main__.Student at 0x283b748c388>"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "# 创建出Student的实例:\n",
    "bart = Student()\n",
    "bart"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "__main__.Student"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "Student"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以自由地给一个实例变量绑定属性，比如，给实例bart绑定一个name属性："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Bart Simpson'"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bart.name = 'Bart Simpson'\n",
    "bart.name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "由于类可以起到模板的作用，因此，可以在创建实例的时候，把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的 __ init __ 方法，在创建实例的时候，就把name，score等属性绑上去："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Student(object):\n",
    "\n",
    "    def __init__(self, name, score):\n",
    "        self.name = name\n",
    "        self.score = score"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "__注意：特殊方法“ __ init __ ”前后分别有两个下划线！！！__"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "注意到 __ init __ 方法的第一个参数永远是self，表示创建的实例本身，因此，在 __ init __ 方法内部，就可以把各种属性绑定到self，因为self就指向创建的实例本身。\n",
    "\n",
    "有了 __ init __ 方法，在创建实例的时候，就不能传入空的参数了，必须传入与 __ init __ 方法匹配的参数，但self不需要传，Python解释器自己会把实例变量传进去："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Bart Simpson'"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bart = Student('Bart Simpson', 59)\n",
    "bart.name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "59"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bart.score"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "和普通的函数相比，在类中定义的函数只有一点不同，就是第一个参数永远是实例变量self，并且，调用时，不用传递该参数。除此之外，类的方法和普通函数没有什么区别，所以，你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 数据封装\n",
    "\n",
    "面向对象编程的一个重要特点就是数据封装。在上面的Student类中，每个实例就拥有各自的name和score这些数据。我们可以通过函数来访问这些数据，比如打印一个学生的成绩："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Bart Simpson: 59\n"
     ]
    }
   ],
   "source": [
    "def print_score(std):\n",
    "    print('%s: %s' % (std.name, std.score))\n",
    "    \n",
    "print_score(bart)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "但是，既然Student实例本身就拥有这些数据，要访问这些数据，就没有必要从外面的函数去访问，可以直接在Student类的内部定义访问数据的函数，这样，就把“数据”给封装起来了。这些封装数据的函数是和Student类本身是关联起来的，我们称之为__类的方法__："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Student(object):\n",
    "\n",
    "    def __init__(self, name, score):\n",
    "        self.name = name\n",
    "        self.score = score\n",
    "\n",
    "    def print_score(self):\n",
    "        print('%s: %s' % (self.name, self.score))\n",
    "        "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "要定义一个方法，除了第一个参数是self外，其他和普通函数一样。要调用一个方法，只需要在实例变量上直接调用，除了self不用传递，其他参数正常传入："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Bart Simpson: 59\n"
     ]
    }
   ],
   "source": [
    "bart = Student('Bart Simpson', 59)\n",
    "bart.print_score()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "1m这样一来，我们从外部看Student类，就只需要知道，创建实例需要给出name和score，而如何打印，都是在Student类的内部定义的，这些数据和逻辑被“封装”起来了，调用很容易，但却不用知道内部实现的细节。\n",
    "\n",
    "封装的另一个好处是可以给Student类增加新的方法，比如get_grade："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Student(object):\n",
    "\n",
    "    def __init__(self, name, score):\n",
    "        self.name = name\n",
    "        self.score = score\n",
    "\n",
    "    def print_score(self):\n",
    "        print('%s: %s' % (self.name, self.score))\n",
    "    \n",
    "    def get_grade(self):\n",
    "        if self.score >= 90:\n",
    "            return 'A'\n",
    "        elif self.score >= 60:\n",
    "            return 'B'\n",
    "        else:\n",
    "            return 'C'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Lisa A\n",
      "Bart C\n"
     ]
    }
   ],
   "source": [
    "# 上面get_grade方法可以直接在实例变量上调用，不需要知道内部实现细节:\n",
    "lisa = Student('Lisa', 99)\n",
    "bart = Student('Bart', 59)\n",
    "print(lisa.name, lisa.get_grade())\n",
    "print(bart.name, bart.get_grade())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 小结\n",
    "类是创建实例的模板，而实例则是一个一个具体的对象，各个实例拥有的数据都互相独立，互不影响；\n",
    "\n",
    "方法就是与实例绑定的函数，和普通函数不同，方法可以直接访问实例的数据；\n",
    "\n",
    "通过在实例上调用方法，我们就直接操作了对象内部的数据，但无需知道方法内部的实现细节。\n",
    "\n",
    "和静态语言不同，Python允许对实例变量绑定任何数据，也就是说，对于两个实例变量，虽然它们都是同一个类的不同实例，但拥有的变量名称都可能不同："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "8"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bart = Student('Bart Simpson', 59)\n",
    "lisa = Student('Lisa Simpson', 87)\n",
    "bart.age = 8\n",
    "bart.age"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [
    {
     "ename": "AttributeError",
     "evalue": "'Student' object has no attribute 'age'",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mAttributeError\u001b[0m                            Traceback (most recent call last)",
      "\u001b[1;32m<ipython-input-14-71701b4dc9c2>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m\u001b[0m\n\u001b[1;32m----> 1\u001b[1;33m \u001b[0mlisa\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mage\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m",
      "\u001b[1;31mAttributeError\u001b[0m: 'Student' object has no attribute 'age'"
     ]
    }
   ],
   "source": [
    "lisa.age"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# 访问限制\n",
    "\n",
    "在Class内部，可以有属性和方法，而外部代码可以通过直接调用实例变量的方法来操作数据，这样，就隐藏了内部的复杂逻辑。\n",
    "\n",
    "但是，从前面Student类的定义来看，外部代码还是可以自由地修改一个实例的name、score属性："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "bart = Student('Bart Simpson', 59)\n",
    "bart.score"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "bart.score = 99\n",
    "bart.score"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果要让内部属性不被外部访问，可以把属性的名称前加上两个下划线 __ ，在Python中，实例的变量名如果以 ______ (双下划线)开头，就变成了一个__私有变量（private)__，只有内部可以访问，外部不能访问，所以，我们把Student类改一改："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Student(object):\n",
    "\n",
    "    def __init__(self, name, score):\n",
    "        self.__name = name\n",
    "        self.__score = score\n",
    "\n",
    "    def print_score(self):\n",
    "        print('%s: %s' % (self.__name, self.__score))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "改完后，对于外部代码来说，没什么变动，但是已经无法从外部访问实例变量.__ name和实例变量 .__ score了："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "bart = Student('Bart Simpson', 59)\n",
    "bart.__name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这样就确保了外部代码不能随意修改对象内部的状态，这样通过访问限制的保护，代码更加健壮。\n",
    "\n",
    "但是如果外部代码要获取name和score怎么办？可以给Student类增加__get_name__和__get_score__这样的方法："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Student(object):\n",
    "    \n",
    "    def get_name(self):\n",
    "        return self.__name\n",
    "\n",
    "    def get_score(self):\n",
    "        return self.__score"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "如果又要允许外部代码修改score怎么办？可以再给Student类增加__set_score__方法："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Student(object):\n",
    "    def set_score(self, score):\n",
    "        self.__score = score"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "你也许会问，原先那种直接通过bart.score = 99也可以修改啊，为什么要定义一个方法大费周折？__因为在方法中，可以对参数做检查，避免传入无效的参数__："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Student(object):\n",
    "\n",
    "    def set_score(self, score):\n",
    "        if 0 <= score <= 100:\n",
    "            self.__score = score\n",
    "        else:\n",
    "            raise ValueError('bad score')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "需要注意的是，在Python中，变量名类似 __ xxx __ 的，也就是以双下划线开头，并且以双下划线结尾的，是特殊变量，特殊变量是可以直接访问的，不是private变量，所以，不能用 __ name __ 、__ score __ 这样的变量名。\n",
    "\n",
    "有些时候，你会看到以一个下划线开头的实例变量名，比如 _name，这样的实例变量外部是可以访问的，但是，按照约定俗成的规定，当你看到这样的变量时，意思就是，“虽然我可以被访问，但是，请把我视为私有变量，不要随意访问”。\n",
    "\n",
    "双下划线开头的实例变量是不是一定不能从外部访问呢？其实也不是。不能直接访问 __ name是因为Python解释器对外把 __ name变量改成了 _ Student __ name，所以，仍然可以通过  _ Student __ name来访问 __ name变量："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    " bart._Student__name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "但是强烈建议你不要这么干，因为不同版本的Python解释器可能会把__name改成不同的变量名。\n",
    "\n",
    "总的来说就是，Python本身没有任何机制阻止你干坏事，一切全靠自觉。\n",
    "\n",
    "最后注意下面的这种__错误写法__："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Bart Simpson'"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "class Student(object):\n",
    "    \n",
    "    def __init__(self, name, score):\n",
    "        self.__name = name\n",
    "        self.__score = score\n",
    "    def get_name(self):\n",
    "        return self.__name\n",
    "\n",
    "    def get_score(self):\n",
    "        return self.__score\n",
    "\n",
    "bart = Student('Bart Simpson', 59)\n",
    "bart.get_name()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'New Name'"
      ]
     },
     "execution_count": 16,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bart.__name = 'New Name' \n",
    "bart.__name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "表面上看，外部代码“成功”地设置了 __ name变量，但实际上这个 __ name变量和class内部的 __ name变量不是一个变量！内部的 __ name变量已经被Python解释器自动改成了 _ Student __ name，而外部代码给bart新增了一个 __ name变量。不信试试："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Bart Simpson'"
      ]
     },
     "execution_count": 17,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "bart.get_name() # get_name()内部返回self.__name"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 练习\n",
    "请把下面的Student对象的gender字段对外隐藏起来，用get_gender()和set_gender()代替，并检查参数有效性："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Student(object):\n",
    "    def __init__(self, name, gender):\n",
    "        self.name = name\n",
    "        self.gender = gender\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "测试成功!\n"
     ]
    }
   ],
   "source": [
    "class Student(object):\n",
    "    def __init__(self, name, gender):\n",
    "        self.name = name\n",
    "        self.__gender = gender    #让gender属性对外部隐藏\n",
    "    def get_gender(self):\n",
    "        return self.__gender         \n",
    "    def set_gender(self, gender):\n",
    "        if gender == 'male' or gender == 'female':     #对gender的参数进行有效性检查\n",
    "            self.__gender = gender\n",
    "        else:\n",
    "            raise ValueError('incorrect gender')\n",
    "\n",
    "# 测试:\n",
    "bart = Student('Bart', 'male')\n",
    "if bart.get_gender() != 'male':\n",
    "    print('测试失败!')\n",
    "else:\n",
    "    bart.set_gender('female')\n",
    "    if bart.get_gender() != 'female':\n",
    "        print('测试失败!')\n",
    "    else:\n",
    "        print('测试成功!')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
